#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------- #
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org)             #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

require 'rexml/document'
include REXML

@vmname = nil
@xml_root = nil

# Sets @vmname _one-VMID_ 
def vmname
  vmid = @xml_root.elements["VMID"]  
  return "one-#{vmid.text}" if vmid 
  #otherwise...
  $stderr.puts "Error: could not name the VM"
  return nil
end
 
# Returns a +string+ containing the arguments of VBoxManage create command to create and register a VM in VBox
def create_args
  create_args = "--name #{@vmname} " 
  create_args += "--register" #used for a instantly register of the VM
  return create_args
end

# Returns a +string+ containing the arguments of VBoxManage modify command that configure an existing VM according to the deployment file options.
def modify_args
  
  #VBoxManage modifyvm VMname ARGS
  modify_args = "";
  	
  memory = @xml_root.elements["MEMORY"]#amount of RAM in MB"
  modify_args += "--memory #{memory.text} " if memory

  vcpu = @xml_root.elements["VCPU"] #number of virtual cpus
  modify_args += "--cpus #{vcpu.text} " if vcpu

  pae = @xml_root.elements["FEATURES/PAE"] #Physical adress extension
  modify_args += (pae.text == "yes"? "--pae on ":"--pae off ") if pae
  
  acpi = @xml_root.elements["FEATURES/ACPI"] #Advanced configuration and power interface"
  modify_args += (acpi.text == "yes"? "--acpi on ":"--acpi off ") if acpi


  #NIC     
  #NETWORK, IP, TARGET, SCRIPT are unsupported
  
  #loops until all interfaces defined are added.
  niface = 1 #number of interface
  @xml_root.each_element('NIC') do | nic |
    
    #specifies which host interface the given network interface will use"
    bridge = nic.elements["BRIDGE"]
    
    if bridge
      modify_args += "--nic#{niface} bridged "
      modify_args += "--bridgeadapter#{niface} #{bridge.text} " 
      mac = nic.elements["MAC"] #normally random address given by VirtualBox
      modify_args += "--macaddress#{niface} #{(mac.text).delete(":")} " if mac
      niface += 1
    else 
      modify_args += "--nic#{niface} nat "
    end
    
  end
  
  #INPUT UNSUPPORTED

  #GRAPHICS (VRDP)
  #loops through all the GRAPHICS sections
  #until it finds a "vrdp" one. Then adds the
  #correct arguments and breaks out of loop.
  @xml_root.each_element('GRAPHICS') do | graphics |
    if graphics.elements["TYPE"] and graphics.elements["TYPE"].text == "vrdp"
      modify_args += "--vrdp on "
      
      vrdp_addr = graphics.elements["LISTEN"]#IP where the VRDP server will bind to
      modify_args += "--vrdpaddress #{vrdp_addr.text} " if vrdp_addr
      
      vrdp_port = graphics.elements["PORT"]# 0 means 3389, standard port for RDP
      modify_args += (vrdp_port != nil ? "--vrdpport #{vrdp_port.text} " : "-- vrdpport default ")
      
      break
    
    end    
  end


  #RAW
  #Loops through all RAW sections. For those
  #of type "vbox", we add the data arguments.

  @xml_root.each_element('RAW') do | raw |
      if raw.elements["TYPE"] and raw.elements["TYPE"].text == "vbox"
        data = raw.elements["DATA"]
        modify_args += data.text if data
      end
    end
  return "#{@vmname} #{modify_args}" if modify_args.size > 0
  return nil
end
  
def start_args
  # Return first graphics section
  @xml_root.each_element('GRAPHICS') do | graphics |
    if graphics.elements["TYPE"]
      type = graphics.elements["TYPE"].text
      case type
      when "sdl" then return "--type sdl"
      else return "--type headless"
      end
    end
  end
  return "--type headless"
end



#Return an array of strings to close the mediums, arguments of VBoxManage closemedium
def closemedium_args
  disk_number = 0
  basedir = `dirname #{domain_filename @vmname}`.chomp
  closemedium_args_array = []

  @xml_root.each_element('DISK') do | disk |
      closemedium_args=""
      
      type = disk.elements["TYPE"] #medium type: "disk|dvd|floppy"
      if type
        closemedium_args += "#{type.text.downcase} "
      else 
        $stderr.puts "Error: disk #{disk_number} type not specified"
        return nil
      end
      
      closemedium_args += "#{basedir}/disk.#{disk_number} "        
      
      closemedium_args_array << closemedium_args
      
      disk_number += 1
    end
  return closemedium_args_array
  
end
  
# Returns an array with the controllers that need to be added.
def controllers_to_add 
  toadd = []  
  @xml_root.each_element('DISK') do |disk|
      bus = disk.elements["BUS"]
      toadd << bus.text if bus
    end #each disk
  
  #do not return duplicate controller names
  return toadd.uniq
  
end



# -- Unused. _device_number_ set to 0.
# def device_number target
#   return target[2] - 97
# end
def port_number target
  return target[2].ord - 97
end

# Convert disk types to VBox format.
# ===Parameters:
# * _type_: Disk type in ONE format
def convert_type type
    type = type.downcase
  case type
	when "disk" then return "hdd"
	when "dvd" then return "dvddrive"
	when "floppy" then return "fdd"
	else return nil
  end
end


def disk_locations
  disk_number = 0
  basedir = `dirname #{domain_filename @vmname}`.chomp
  disk_locations_array = []
  @xml_root.each_element('DISK') do |disk|
    disk_locations_array << "#{basedir}/disk.#{disk_number}"
    disk_number += 1
  end
  return disk_locations_array
end

# Returns an array with the necessary arguments to call +storageattach+.
def storageattach_args  
  
  disk_number = 0
  basedir = `dirname #{domain_filename @vmname}`.chomp
  storageattach_args_array = []
  @xml_root.each_element('DISK') do |disk|
    
    bus = disk.elements["BUS"]
    target = disk.elements["TARGET"]
    type = disk.elements["TYPE"]
    storageattach_args = @vmname
    
    #if these are not defined we cannot call +storageattach+
    if bus and target and type
      storageattach_args += " --storagectl ONE-#{bus.text} "
      storageattach_args += "--port #{port_number target.text} " 
      storageattach_args += "--device 0 "
      storageattach_args += "--type #{convert_type type.text} "
      #storageattach_args += "--medium #{source.text} "
      storageattach_args += "--medium #{basedir}/disk.#{disk_number} "
      readonly = disk.elements["READONLY"]
      storageattach_args += (readonly.text == "yes"? "--mtype immutable " : "--mtype normal ") if readonly
     
      storageattach_args_array << storageattach_args

    else

      $stderr.puts "Error attaching storage: bus, target or type not specified in disk #{disk_number}" 
      return nil

    end #if

    disk_number += 1

  end #each_element

  return storageattach_args_array
end


# Returns an array with the necessary arguments to call +storagedettach+.
def storagedettach_args
 
  disk_number = 0
  storagedettach_args_array = []
  @xml_root.each_element('DISK') do |disk|
    
    bus = disk.elements["BUS"]
    target = disk.elements["TARGET"]
    type = disk.elements["TYPE"]
    storagedettach_args = @vmname
    
    #if these are not defined we cannot call +storageattach+
    if bus and target
      storagedettach_args += " --storagectl ONE-#{bus.text} "
      storagedettach_args += "--port #{port_number target.text} " 
      storagedettach_args += "--device 0 "
      storagedettach_args += "--medium none"
     
      storagedettach_args_array << storagedettach_args

    else

      $stderr.puts "Error dettaching storage: bus or target not specified in disk #{disk_number}"
      return nil

    end #if

    disk_number += 1

  end #each_element

  return storagedettach_args_array

end


def supports_acpi?
  acpi = @xml_root.elements["FEATURES/ACPI"] 
  return true if acpi and acpi.text == "yes"
  return nil
end


#----------------------------------DOMAIN FUNCTIONS ----------------------------#

# Init one-vbox environment
# Finds the domain file for this VMID
# Inits the @vmname and @xml_root
def init domain_fn=nil,vm_name=nil
  return if !domain_fn and !vm_name
  @vmname = vm_name if vm_name

  domain_fn = domain_filename @vmname if !domain_fn
  
  begin
    domain=File.new(domain_fn ,"r")
  rescue
    $stderr.puts "Unable to open domain file:"
    $stderr.puts "domain_fn: #{domain_fn}. vmname: #{vm_name}"
    exit 1
  end
  
  deployment_xml = Document.new(domain)
  @xml_root = deployment_xml.root
  @vmname = vmname if !vm_name
  return domain_fn
end


# Init one-vbox setting @vmname
# Does not enable XML treatment
def init_no_xml vmid
  @vmname = "one-#{vmid}"
end


def load_domains
  begin
    f = File.new(DOMAINS_FILE,"r")
    @domains_hash = Marshal.load(f.read)
    f.close
  rescue
    @domains_hash = {}
  end
end


def save_domains
  f = File.new(DOMAINS_FILE,"w")
  marshal_dump = Marshal.dump(@domains_hash)
  f.write(marshal_dump)
  f.close
end


def domain_filename vm_name
  load_domains if !@domains_hash
  @domains_hash[vm_name]
end


def add_domain vm_name,domain
  load_domains if !@domains_hash
  @domains_hash[vm_name] = domain
  save_domains
end


def remove_domain vm_name
  load_domains if !@domains_hash
  @domains_hash.delete vm_name
  save_domains
end


# Tests parser
def test
  open_deployment_xml "deployment.0"
  
  puts "**Create args**"
  puts create_args

  puts "**Modify args**"
  puts modify_args

  puts "**Openmedium**"
  puts openmedium_args

  puts "**Closemedium**"
  puts closemedium_args

  puts "**Add controllers**"
  puts controllers_to_add 

  puts "**Attach storage**"
  puts storageattach_args
end
